/*********************************************************************************
 * Software License Agreement (BSD License)
 * Copyright (c) 2012, Carlos D. Correa (info@ngraph.org)
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of netzen nor the
 *    names of its contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *********************************************************************************/
#include "glheaders.h"

#include <assert.h>
#include <string.h>
#include <math.h>
#include "renderUtils.h"
#include "mouseUtils.h"
#include "graph.h"
#include "view.h"
#include "ui/uiUtils.h"
#include "controller.h"
#include <sstream>

class Histogram: public View {
protected:
	string property;
	int numBins;
	float *frequencies;
	int *selections;
	vector<PropertyBag *> *binning;
	float maxFreq, minFreq, sumFreq;
	bool inited;
	string selectionProperty;
public:
	Histogram(const char *title) : View("Histogram", title) {
		setPropertyInt("Data.Number Bins", 20);
		setPropertyInt("Grid.Tics X", 5);
		setPropertyInt("Grid.Tics Y", 5);
		setPropertyBool("Grid.Logscale X", false);
		setPropertyBool("Grid.Logscale Y", false);
		setPropertyBool("Grid.Draw Grid X", true);
		setPropertyBool("Grid.Draw Grid Y", true);
		numBins = 20;
		frequencies = 0;
		binning = 0;
		selections = 0;
    setMargins(120, 40, 40, 80);
		inited = false;
	}
	
	
	
	void update(string /* property */) {
		updateHistogram();
	}
	
	void drawXTics(string propertyX, int numTicsX){
		
		vector<float> values;
		vector<string> names;
		// Returns locations (in unit square) and labels of tics
		if(getPropertyBool("Grid.Logscale X")) {
			getLogTics(propertyX, numTicsX, values, names);
		} else {
			getTics(propertyX, numTicsX, values, names);
		}
		for(unsigned int i=0;i<values.size();i++) {
			float x = values[i];
			glBegin(GL_LINES);
			{
				glVertex2f(x, ypixels(-10.0));
				glVertex2f(x, ypixels(-5.0));
			}
			glEnd();
			if(propertyX!="Default") {
				string str = names[i];
				renderText(x, ypixels(-20), (char *) str.c_str(), "center");
			}
    }
		
		if(getPropertyBool("Grid.Draw Grid X")) {
			glColor4f(0.6,0.6,0.6,1.0);
			glLineStipple(1, 0xF0F0);
			glEnable(GL_LINE_STIPPLE);
			glBegin(GL_LINES);
			for(unsigned int i=0;i<values.size();i++) {
				float x = values[i];
				glVertex2f(x, 0);
				glVertex2d(x, 1);
			}
			glEnd();
			glDisable(GL_LINE_STIPPLE);
			glColor4f(0.0,0.0,0.0,1.0);
		}
		
	}
	
	void drawYTics(Interval interval, int numTicsY) {
		
		vector<float> values;
		vector<string> names;
		// Returns locations (in unit square) and labels of tics
		if(getPropertyBool("Grid.Logscale Y")) {
			getLogTics(interval, numTicsY, values, names);
		} else {
			getTics(interval, numTicsY, values, names);
		}
		for(unsigned int i=0;i<values.size();i++) {
			float y = values[i];
			glBegin(GL_LINES);
			{
				glVertex2f(xpixels(-5.0), 0.0 + (1.0)*y);
				glVertex2f(xpixels(-10.0), 0.0 + (1.0)*y);
			}
			glEnd();
			string str = names[i];
			renderText(xpixels(-10.0), y, (char* )  str.c_str(), "right");
    }
		
		if(getPropertyBool("Grid.Draw Grid Y")) {
			glColor4f(0.6,0.6,0.6,1.0);
			glLineStipple(1, 0xF0F0);
			glEnable(GL_LINE_STIPPLE);
			glBegin(GL_LINES);
			for(unsigned int i=0;i<values.size();i++) {
				float y = values[i];
				glVertex2f(0,y);
				glVertex2d(1,y);
			}
			glEnd();
			glDisable(GL_LINE_STIPPLE);
			glColor4f(0.0,0.0,0.0,1.0);
		}
	}
	
	void updateSelection(int bin, int marker) {
		if(!binning) return;
		if(bin<0 || bin>=numBins) return;
		selections[bin] = marker;
		vector<PropertyBag*> selection;
		for(unsigned int i = 0; i < binning[bin].size();i++) {
			PropertyBag * element = binning[bin][i];
			selection.push_back(element);
		}
		selectElements(selection, selectionMarker);
	}
	void resetSelection(int bin, int marker) {
		if(!binning) return;
		if(bin<0 || bin>=numBins) return;
		if(selections[bin]==marker) {
			selections[bin] = 0;
		}
		vector<PropertyBag*> selection;
		for(unsigned int i = 0; i < binning[bin].size();i++) {
			PropertyBag * element = binning[bin][i];
			int selected = element->getPropertyInt(selectionProperty);
			if(selected==marker) {
				selection.push_back(element);
			}
		}
		selectElements(selection, 0);
	}
	
	void render() {
		string curProp = PROPERTY(getPropertyString("Data.X"));		
		float minv, maxv;
		graph->getStatManager()->getMinMaxFloat(curProp, minv, maxv);
		
		if(!inited || curProp!=property) {
			updateHistogram();
			inited = true;
		}
    // Draw axes
    glLineWidth(1);
    //glColor4f(0,0,0,0.95);
    glColor4f(fColor_front,fColor_front,fColor_front,0.95);
    glBegin(GL_LINE_STRIP);
    {
			glVertex2f(0.0, ypixels(-5.0)); // bottom left
			glVertex2f(1.0, ypixels(-5.0)); // bottom right
    }
    glEnd();
    glBegin(GL_LINE_STRIP);
    {
			glVertex2f(xpixels(-5.0), 0.0); // bottom left
			glVertex2f(xpixels(-5.0), 1.0); // top left
    }
    glEnd();

		
		// Draw Tics X
    int numTicsX = getPropertyInt("Grid.Tics X");
    drawXTics(curProp, numTicsX);
		
		
    // Draw Tics Y
    int numTicsY = getPropertyInt("Grid.Tics Y");
    drawYTics(Interval(minFreq, maxFreq), numTicsY);
		
		
		// Draw hist.bars
		glColor4f(0,0.2,0.7,0.8);
		if(frequencies && maxFreq>0) {
			float l0 = log10(minFreq);
			float l1 = log10(maxFreq);
			bool logY = getPropertyBool("Grid.Logscale Y");
			float dx = 0.5/(float) numBins;
			glBegin(GL_QUADS);
			for(int i=0;i<numBins;i++) {
				float x = ((float) i + 0.5) / (float) numBins;
				float y = frequencies[i]/maxFreq;
				float rgb[3];
				rgb[0]=rgb[1]=rgb[2] = 0.5;
				if(logY) {
					y = (log10(frequencies[i])-l0)/(l1-l0);
				}
        if(dragMode==DRAG) {
					if(inSelection(x,y)) {
						getSelectionColor(selectionMarker, rgb);
						updateSelection(i, selectionMarker);
					} else {
						resetSelection(i, selectionMarker);
					}
				}
				int selected = selections[i];
				if(selected > 0) {
					getSelectionColor(selected, rgb);
				}
				glColor3f(rgb[0], rgb[1], rgb[2]);				
				glVertex2f(x-dx, 0);
				glVertex2f(x-dx, y);
				glVertex2f(x+dx, y);
				glVertex2f(x+dx, 0);
			}
			glEnd();
			glColor4f(0,0,0,0.8);
			glBegin(GL_LINE_LOOP);
			glVertex2f(0,0);
			for(int i=0;i<numBins;i++) {
				float x = ((float) i + 0.5) / (float) numBins;
				float y = frequencies[i]/maxFreq;
				if(logY) {
					y = (log10(frequencies[i])-l0)/(l1-l0);
				}
				glVertex2f(x-dx, 0);
				glVertex2f(x-dx, y);
				glVertex2f(x+dx, y);
				glVertex2f(x+dx, 0);
			}
			glVertex2f(1,0);
			glEnd();
		}

		glColor4f(0,0.0,0.0,1.0);
		
		
    setFont(getCurrentFont(), getCurrentFontSize(), "bold");
		renderText(0.5, ypixels(-30.0), (char*) property.c_str(), "center");
    renderText(0.0, 1.0 +  ypixels(2.0), "Frequency");
    setFont(getCurrentFont(), getCurrentFontSize(), "normal");
	}
	virtual ElementIterator *createIterator() = 0;
	
	
	virtual void updateHistogram() {
		string curProp = PROPERTY(getPropertyString("Data.X"));
		property = curProp;
		if(frequencies) delete frequencies;
		if(selections) delete selections;
		if(binning) delete[] binning;
		print("Creating histogram for property [%s]\n", property.c_str());
		numBins = max(1,getPropertyInt("Data.Number Bins"));
		frequencies = new float[numBins];
		selections = new int[numBins];
		binning = new vector<PropertyBag*>[numBins];
		
		vector<float> frequencyVector;
		for(int i=0;i<numBins;i++) {
			frequencies[i] = 0;		
		}
		bool logX = getPropertyBool("Grid.Logscale X");
		ElementIterator *it = createIterator();
		for(it->begin();it->hasMoreElements();) {
			PropertyBag *element = it->next();
			float normalized = graph->getAsFloat(element, property, 1.0, true);
			if(logX) {
				float minv, maxv;
				getMinMax(property, minv, maxv);
				float v0, v1;
				getMinMaxLog(property, v0, v1);
				float x = minv + (maxv-minv)*normalized;
				normalized = (log10(x)-log10(v0))/(log10(v1)-log10(v0));
			}
			int bin = (int) (normalized*(float) numBins);
			if(bin>=numBins) bin = numBins-1;
			if(bin<0) bin = 0;
			binning[bin].push_back(element);
			frequencies[bin]++;
		}
		graph->destroyIterator(it);
		for(int i=0;i<numBins;i++) {
			selections[i] = 0;
			frequencyVector.push_back(frequencies[i]);
		}
															
		maxFreq = 0;
		minFreq = 1e20;
		sumFreq = 0;
		for(int i=0;i<numBins;i++) {
			sumFreq += frequencies[i];
			maxFreq = max(maxFreq, frequencies[i]);
			minFreq = min(minFreq, frequencies[i]);
		}
		if(getPropertyBool("Grid.Logscale Y")) {
			getMinMaxLog(frequencyVector, minFreq, maxFreq);
		}
	}
};

class NodeHistogram: public Histogram {
public:
	NodeHistogram(const char *title): Histogram(title) {
		setPropertyStringList("Data.X", "Default", "node");
		selectionProperty = "node.selected";
	}
	virtual ElementIterator *createIterator() {
		return graph->createNodeIterator();
	}

};

class EdgeHistogram: public Histogram {
public:
	EdgeHistogram(const char *title): Histogram(title) {
		setPropertyStringList("Data.X", "Default", "edge");
		selectionProperty = "node.selected";
	}
	virtual ElementIterator *createIterator() {
		return graph->createEdgeIterator();
	}
};

View *createNodeHistogram(string name) {
	NodeHistogram *view =new NodeHistogram(name.c_str());
	view->setLocation(0.0, 1.0);
	view->setSize(1.0, 0.7);
	view->setVisible(true);
	view->addCloseButton();
	view->addSelectionWidget();
	view->addTransparentButton();
	return view;	
}

View *createEdgeHistogram(string name) {
	EdgeHistogram *view =new EdgeHistogram(name.c_str());
	view->setLocation(0.0, 1.0);
	view->setSize(1.0, 0.7);
	view->setVisible(true);
	view->addCloseButton();
	view->addSelectionWidget();
	view->addTransparentButton();
	return view;	
}

void registerHistograms(Controller *controller) {
	controller->addViewCreator("Node Histogram", createNodeHistogram);
	controller->addViewCreator("Edge Histogram", createEdgeHistogram);
}

